package com.demo.ipcSocket;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.demo.entity.PhysicalPreventionLog;
import com.demo.response.Result;
import com.demo.service.IPhysicalPreventionLogService;
import com.demo.service.IVtePreventiveDoctorAdviceService;
import com.demo.service.IVtePreventiveService;
import com.demo.websocket.WebSocketServer;
import com.demo.websocket.WebsocketIpcServer;
import com.demo.websocket.vo.VteIpcMessageVO;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author MT
 * @date 2019-12-09 13:21
 */
@Slf4j
@Component
@Data
@NoArgsConstructor
@PropertySource("classpath:socket.properties")
public class SocketServer {

    @Value("${port}")
    private Integer port;
    @Value("${keepAlive}")
    private boolean keepAlive;
    @Autowired
    WebSocketServer webSocketServer;
    @Autowired
    IVtePreventiveDoctorAdviceService adviceService;
    @Autowired
    IVtePreventiveService iVtePreventiveService;
    @Autowired
    WebsocketIpcServer websocketIpcServer;
    @Autowired
    IPhysicalPreventionLogService iPhysicalPreventionLogService;
    @Autowired
    IVtePreventiveDoctorAdviceService vtePreventiveDoctorAdviceService;

    private boolean started;
    private ServerSocket serverSocket;
    private ExecutorService executorService = Executors.newFixedThreadPool(100);

    public void start() {
        start(null);
    }


    public void start(Integer port) {
        log.info("port: 配置端口{}, 主动指定启动端口{}", this.port, port);
        try {
            this.serverSocket = new ServerSocket(((port == null) ? this.port : port).intValue());
            this.started = true;
            log.info("Socket服务已启动，占用端口{}", Integer.valueOf(this.serverSocket.getLocalPort()));
        } catch (IOException e) {
            log.error("端口冲突，异常信息{}", e);
            System.exit(0);
        }
        while (true) {
            Socket socket = null;
            try {
                socket = this.serverSocket.accept();
                socket.setKeepAlive(this.keepAlive);
                Socket finalSocket1 = socket;
                this.executorService.submit(() -> {
                    ClientSocket clientSocket = null;
                    try {
                        clientSocket = new ClientSocket();
                        int len = 0;
                        byte[] bytesArr = new byte[1024];
                        StringBuffer sb = new StringBuffer();
                        log.info("进入阻塞代码");
                        while ((len = finalSocket1.getInputStream().read(bytesArr)) != -1) {
                            try {
                                log.info("读取字节码：" + new String(bytesArr, 0, len, "UTF-8"));
                                sb.append(new String(bytesArr, 0, len, "UTF-8"));
                                log.info("接受本次消息本次消息头" + (bytesArr[0] & 0xFF));
                                int dataLen = 0;
                                int mssageHeader = bytesArr[0] & 0xFF;
                                if (mssageHeader == 255) {
                                    log.info("接受本次消息长度 1 " + (bytesArr[1] & 0xFF));
                                    byte[] dataLenBytes = new byte[4];
                                    dataLenBytes[0] = bytesArr[2];
                                    dataLenBytes[1] = bytesArr[3];
                                    dataLenBytes[2] = bytesArr[4];
                                    dataLenBytes[3] = bytesArr[5];
                                    dataLen = SocketHandler.byte2int(dataLenBytes);
                                    log.info("接受整体消息长度 toint：" + dataLen);
                                    log.info("接受整体消息长度 toint：" + SocketHandler.byteArrayToInt(dataLenBytes));
                                    String key = new String(bytesArr, 6, dataLen);
                                    log.info("接受整体消息：" + key);
                                    clientSocket.setSocket(finalSocket1);
                                    log.info("消息链接" + finalSocket1.toString());
                                    clientSocket.setInputStream(new DataInputStream(finalSocket1.getInputStream()));
                                    clientSocket.setOutputStream(new DataOutputStream(finalSocket1.getOutputStream()));
                                    clientSocket.setKey(new String(bytesArr, 6, dataLen, "UTF-8"));
                                    try {
                                        log.info("开始执行业务代码");
                                        log.info("aaa");
                                        handleMessage(clientSocket);
                                        log.info("结束执行业务代码");
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                        log.error("业务代码发生异常", e);
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                                log.error("获取字节码代码异常", e);
                            }
                        }
                        clientSocket.shutdownStream();
                    } catch (Exception e) {
                        e.printStackTrace();
                        if (clientSocket != null)
                            clientSocket.shutdownStream();
                    }
                });
            } catch (IOException e) {
                if (socket != null) {
                    ClientSocket register = SocketHandler.register(socket);
                    if (register != null) {
                        register.shutdownStream();
                    }
                }
                e.printStackTrace();
                log.error("发送设备消息错误", e);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("消息错误", e);
            }
        }
    }

    /**
     * IPC设备消息处理
     *
     * @param socket
     */
    public void handleMessage(ClientSocket socket) {
        if (socket == null) {
            log.error("本次消息格式错误，抛弃");
            return;
        }
        String message = socket.getKey().trim().replaceAll("\n", "").replaceAll("\r", "").replace("“", "\"");
        log.info("handleMessage 客户端消息内容 = {}", message);

        VteIpcMessageVO clientMessage = JSON.parseObject(message, VteIpcMessageVO.class);
        switch (clientMessage.getType()) {
            case 1:
                getTreatmentDoctorAdvice(socket, clientMessage);
                break;
            case 2:
                confirmDoctorAdvice(socket, clientMessage);
                break;
            case 3:
                getIpcRunTimeData(clientMessage, message);
                break;
            case 4:
                adjustSettingParameters(socket, clientMessage);
                break;
            case 5:
                stopIpcTreatment(socket, clientMessage);
                break;
            default:
                defaultHandle(clientMessage, message);
                break;
        }
    }

    /**
     * 3.1 获取治疗医嘱
     * 向服务器请求IPC治疗医嘱，通过病区查询医嘱列表，该协议为MX机型特有
     */
    public void getTreatmentDoctorAdvice(ClientSocket socket, VteIpcMessageVO clientMessage) {
        com.demo.websocket.response.Result result = vtePreventiveDoctorAdviceService.getIPCDoctorAdvice(clientMessage);
        SocketHandler.sendMessage(socket, JSONObject.toJSONString(result));
    }

    /**
     * 3.2 确认选择医嘱
     * 向服务器发送用户在IPC端选择的治疗医嘱，该协议为MX机型特有
     */
    public void confirmDoctorAdvice(ClientSocket socket, VteIpcMessageVO clientMessage) {
        com.demo.websocket.response.Result result = new com.demo.websocket.response.Result(200, "success", 102, (new Date()).getTime());
        if (clientMessage.getAdv() == null) {
            result.setMsg("医嘱不存在");
            SocketHandler.sendMessage(socket, JSONObject.toJSONString(result));
            return;
        }
        DateTime startTime = DateUtil.parse(DateUtil.now(), "yyyy-MM-dd HH:mm:ss");
        DateTime endTime = DateUtil.offsetMinute(startTime, 30);
        PhysicalPreventionLog physicalPreventionLog = new PhysicalPreventionLog(clientMessage.getAdv(), clientMessage.getPatId(), startTime, endTime, clientMessage.getIpcMac());
        iPhysicalPreventionLogService.addPhysicalLog(physicalPreventionLog);
        SocketHandler.sendMessage(socket, JSONObject.toJSONString(result));
    }

    /**
     * 3.3 发送运行数据
     * 每秒向服务器发送IPC设备运行数据，包括设置参数、压力监测数据
     */
    public void getIpcRunTimeData(VteIpcMessageVO clientMessage, String message) {
        log.info("发送运行数据 {}", message);
        try {
            message = convertPatBed(clientMessage, message);
            this.websocketIpcServer.sendToAll(message);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("socket转发 type3 消息转发失败", e);
        }
    }

    /**
     * 3.4 调整设置参数
     * 用户在IPC界面调整运行参数后，发送参数信息
     */
    public void adjustSettingParameters(ClientSocket socket, VteIpcMessageVO clientMessage) {
        com.demo.websocket.response.Result result = new com.demo.websocket.response.Result(200, "调整成功", 104, (new Date()).getTime());
        if (clientMessage.getAdv() == null) {
            result.setMsg("医嘱不存在");
        } else {
            vtePreventiveDoctorAdviceService.updateDoctorAdvicePara(clientMessage.getAdv(), clientMessage.getData().getPara());
        }
        SocketHandler.sendMessage(socket, JSONObject.toJSONString(result));
    }

    /**
     * 3.5 停止IPC治疗
     * 用户在床旁停止IPC治疗后，向服务器发送停止命令
     */
    public void stopIpcTreatment(ClientSocket socket, VteIpcMessageVO clientMessage) {
        com.demo.websocket.response.Result result = new com.demo.websocket.response.Result(200, "停止成功", 105, (new Date()).getTime());
        if (clientMessage.getAdv() == null) {
            result.setMsg("医嘱不存在");
        } else {
            iPhysicalPreventionLogService.updatePhysicalLogStop(clientMessage.getAdv(), DateUtil.parse(DateUtil.now(), "yyyy-MM-dd HH:mm:ss"));
        }
        SocketHandler.sendMessage(socket, JSONObject.toJSONString(result));
    }

    /**
     * 其他消息处理
     */
    public void defaultHandle(VteIpcMessageVO clientMessage, String message) {
        try {
            message = convertPatBed(clientMessage, message);
            this.websocketIpcServer.sendToAll(message);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("其他消息类型转发给 webSocket 错误", e);
        }
    }

    /**
     * 床位字段转换
     * @param clientMessage
     * @param message
     * @return
     */
    private String convertPatBed(VteIpcMessageVO clientMessage, String message) {
        if (StringUtils.isNotBlank(clientMessage.getPatBed()) && clientMessage.getPatBed().contains("-")) {
            JSONObject sourceMessage = JSON.parseObject(message);
            if (sourceMessage.containsKey("pat_bed")) {
                String patBed = sourceMessage.getString("pat_bed");
                if (StringUtils.isNotBlank(patBed) && patBed.contains("-")) {
                    sourceMessage.put("pat_bed", patBed.split("-")[0]);
                }
                message = sourceMessage.toJSONString();
            }
        }
        return message;
    }
}
